﻿using System;
using System.Collections.Generic;
using NaturalKeySpike.Common;

namespace NaturalKeySpike {
    public abstract class Entity {
        public virtual string Name { get; set; }
        public virtual DateTime ModifiedOn { get; set; }
    }

    public class TableA : Entity {
        protected TableA() {
        }

        public TableA(int id) {
            TableAId = id;
            Name = "A" + id;
        }

        private readonly IList<TableB> _tableBs = new List<TableB>();
        private readonly IList<TableC> _tableCs = new List<TableC>();

        public virtual int TableAId { get; protected set; }
        public virtual IReadOnlyList<TableB> TableBs { get { return _tableBs.AsReadOnly(); } }
        public virtual IReadOnlyList<TableC> TableCs { get { return _tableCs.AsReadOnly(); } }

        public virtual TableB AddB(int id) {
            var tableB = new TableB(id);
            _tableBs.Add(tableB);
            tableB.TableA = this;
            return tableB;
        }

        public virtual TableC AddC(int id) {
            var tableC = new TableC(id);
            _tableCs.Add(tableC);
            tableC.TableA = this;
            return tableC;
        }

        public override int GetHashCode() {
            return TableAId;
        }

        public override bool Equals(object obj) {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((TableA)obj);
        }

        protected bool Equals(TableA other) {
            return TableAId == other.TableAId;
        }
    }

    public class TableB : Entity {
        protected TableB() {
        }

        protected internal TableB(int id) {
            TableBId = id;
            Name = "B" + id;
        }

        private readonly IList<TableD> _tableDs = new List<TableD>();

        public virtual int TableBId { get; protected set; }
        public virtual TableA TableA { get; protected internal set; }
        public virtual IReadOnlyList<TableD> TableDs { get { return _tableDs.AsReadOnly(); } }

        public virtual TableD AddD(int id, TableC tableC) {
            if (!Equals(TableA, tableC.TableA)) throw new ArgumentException("tableB and tableC must belong to same TableA parent.");

            var tableD = new TableD(id);

            _tableDs.Add(tableD);
            tableD.TableB = this;

            tableC.Add(tableD);

            return tableD;
        }

        public override int GetHashCode() {
            unchecked {
                return (TableBId * 397) ^ (TableA != null ? TableA.GetHashCode() : 0);
            }
        }

        public override bool Equals(object obj) {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((TableB)obj);
        }

        protected bool Equals(TableB other) {
            return TableBId == other.TableBId && Equals(TableA, other.TableA);
        }
    }

    public class TableC : Entity {
        protected TableC() {
        }

        protected internal TableC(int id) {
            TableCId = id;
            Name = "C" + id;
        }

        private readonly IList<TableD> _tableDs = new List<TableD>();

        public virtual int TableCId { get; protected set; }
        public virtual TableA TableA { get; protected internal set; }
        public virtual IReadOnlyList<TableD> TableDs { get { return _tableDs.AsReadOnly(); } }

        public override int GetHashCode() {
            unchecked {
                return (TableCId * 397) ^ (TableA != null ? TableA.GetHashCode() : 0);
            }
        }

        public override bool Equals(object obj) {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((TableC)obj);
        }

        protected bool Equals(TableC other) {
            return TableCId == other.TableCId && Equals(TableA, other.TableA);
        }

        protected internal virtual void Add(TableD tableD) {
            _tableDs.Add(tableD);
            tableD.TableC = this;
        }
    }

    public class TableD : Entity {
        protected TableD() {
        }

        public TableD(int id) {
            TableDId = id;
            Name = "D" + id;
        }

        public virtual int TableDId { get; protected set; }
        public virtual TableB TableB { get; protected internal set; }
        public virtual TableC TableC { get; protected internal set; }

        public override int GetHashCode() {
            unchecked {
                var hashCode = TableDId;
                hashCode = (hashCode * 397) ^ (TableB != null ? TableB.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (TableC != null ? TableC.GetHashCode() : 0);
                return hashCode;
            }
        }

        public override bool Equals(object obj) {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((TableD)obj);
        }

        protected bool Equals(TableD other) {
            return TableDId == other.TableDId && Equals(TableB, other.TableB) && Equals(TableC, other.TableC);
        }
    }
}
